package com.netx.generics.translation;
import com.netx.generics.basic.Checker;

public class Recognizers {

	public static class StringRecognizer implements Recognizer {
		
		private final String _delimiters;
		private final String _escapes;
		
		public StringRecognizer(String delimiters, String escapes) {
			Checker.checkEmpty(delimiters, "delimiters");
			_delimiters = delimiters;
			// Add 'delimiters' to 'escapes':
			escapes = escapes==null ? "" : escapes;
			for(int i=0; i<delimiters.length(); i++) {
				if(escapes.indexOf(delimiters.charAt(i)) == -1) {
					escapes = escapes + delimiters.charAt(i);
				}
			}
			_escapes = escapes;
		}

		public StringRecognizer(String delimiters) {
			this(delimiters, Token.getEscapeChars());
		}
		
		public Token recognize(Results r, String line, int lineNum, int currentPos) throws RecognizeException {
			if(_delimiters.indexOf(line.charAt(currentPos)) == -1) {
				return null;
			}
			char delimiter = line.charAt(currentPos);
			int initialPos = currentPos++;
			int illegalEscape = -1;
			while(currentPos < line.length()) {
				// End of string:
				if(line.charAt(currentPos) == delimiter) {
					currentPos++;
					if(illegalEscape != -1) {
						r.addError("illegal escape sequence: '\\"+line.charAt(illegalEscape)+"'", new Position(1, illegalEscape));
					}
					return new Token(Token.STRING_CONSTANT, lineNum, initialPos, currentPos, line);
				}
				// Escape:
				else if(line.charAt(currentPos) == '\\') {
					currentPos++;
					if(_escapes.indexOf(line.charAt(currentPos)) == -1) {
						// Don't throw exception, let the recognizer finish reading
						// the string, and position itself at the end of it.
						illegalEscape = currentPos;
					}
					currentPos++;
				}
				else {
					currentPos++;
				}
			}
			// If we got here, the end of String hasn't been found:
			throw new RecognizeException("unterminated String constant", initialPos, currentPos);
		}
	}

	public static class NumberRecognizer implements Recognizer {
		private final boolean _recognizeFloats;
	
		public NumberRecognizer(boolean recognizeFloats) {
			_recognizeFloats = recognizeFloats;
		}
	
		public Token recognize(Results r, String line, int lineNum, int currentPos) throws RecognizeException {
			// Careful with negative numbers:
			int beginPos = currentPos;
			if(line.charAt(currentPos) == '-') {
				currentPos++;
			}
			boolean foundDigits = false;
			while(currentPos<line.length()) {
				if(Character.isDigit(line.charAt(currentPos))) {
					foundDigits = true;
					currentPos++;
				}
				else {
					break;
				}
			}
			if(!foundDigits) {
				return null;
			}
			boolean isFloat = false;
			if(currentPos<line.length() && _recognizeFloats) {
				if(line.charAt(currentPos) == '.') {
					isFloat = true;
					currentPos++;
					if(Character.isDigit(line.charAt(currentPos))) {
						while(currentPos<line.length()) {
							if(Character.isDigit(line.charAt(currentPos))) {
								foundDigits = true;
								currentPos++;
							}
							else {
								break;
							}
						}
					}
					else {
						throw new RecognizeException("expected number, found: '"+line.charAt(currentPos)+"'", currentPos, currentPos);
					}
				}
			}
			return new Token(isFloat ? Token.FLOAT_CONSTANT : Token.INTEGER_CONSTANT, lineNum, beginPos, currentPos, line);
		}
	}

	public static class SeparatorRecognizer implements Recognizer {
		private final String _separators;
		
		public SeparatorRecognizer(String separators) {
			Checker.checkEmpty(separators, "separators");
			_separators = separators;
		}

		public Token recognize(Results r, String line, int lineNum, int currentPos) throws RecognizeException {
			if(_separators.indexOf(line.charAt(currentPos)) != -1) {
				return new Token(Token.SEPARATOR, lineNum, currentPos, currentPos+1, line);
			}
			else {
				return null;
			}
		}
	}

	public static class OperatorRecognizer implements Recognizer {
		private final String _operators;
		
		public OperatorRecognizer(String operators) {
			Checker.checkEmpty(operators, "operators");
			_operators = operators;
		}

		public Token recognize(Results r, String line, int lineNum, int currentPos) throws RecognizeException {
			if(_operators.indexOf(line.charAt(currentPos)) != -1) {
				return new Token(Token.OPERATOR, lineNum, currentPos, currentPos+1, line);
			}
			else {
				return null;
			}
		}
	}

	public static class IdentifierRecognizer implements Recognizer {
		private final boolean _acceptDigits;
		private final String _acceptableStartChars;
		private final String _acceptableChars;
		
		public IdentifierRecognizer(boolean acceptDigits, String acceptableStartChars, String acceptableChars) {
			_acceptDigits = acceptDigits;
			_acceptableStartChars = acceptableStartChars==null ? "" : acceptableStartChars;
			_acceptableChars = acceptableChars==null ? "" : acceptableChars;
		}

		public IdentifierRecognizer(boolean acceptDigits, String acceptableChars) {
			this(acceptDigits, acceptableChars, acceptableChars);
		}

		public Token recognize(Results r, String line, int lineNum, int currentPos) throws RecognizeException {
			if(!Character.isLetter(line.charAt(currentPos)) && _acceptableStartChars.indexOf(line.charAt(currentPos))==-1) {
				return null;
			}
			int beginPos = currentPos;
			for(currentPos++; currentPos < line.length(); currentPos++) {
				if(Character.isLetter(line.charAt(currentPos))) {
					continue;
				}
				if(_acceptDigits && Character.isDigit(line.charAt(currentPos))) {
					continue;
				}
				if(_acceptableChars.indexOf(line.charAt(currentPos)) != -1) {
					continue;
				}
				break;
			}
			return new Token(Token.IDENTIFIER, lineNum, beginPos, currentPos, line);
		}
	}

	//TODO keyword recognizer: takes a String[] and determines, by the keywords,
	// the acceptable characters
}
